随着 AI Agent 连接的服务增多(Slack、GitHub、Jira、MCP 服务器等),工具库迅速膨胀。一个典型的多服务器设置很容易拥有 50+ 工具,在对话开始之前就消耗 55,000+ Token。当模型面对 30+ 名称相似的工具时,工具选择准确性也会下降。
工具搜索工具模式通过按需工具发现解决这个问题:
Spring AI 在2.0版本提供了该机制,可用于实现Tool的渐进加载,更好的为agent服务
- 模型初始只收到一个搜索工具 — 最小化 Token 使用
- 当需要某种能力时,模型用自然语言查询调用搜索工具
- 匹配的工具定义被动态展开到上下文中
- 模型随后可以正常调用被发现的工具
这实现了显著的 Token 节省,同时保持对大型工具目录的访问。
运行时流程
ToolSearchToolCallingAdvisor 扩展了 Spring AI 的 ToolCallingAdvisor,实现了动态工具发现。
运行时流程如下:
- 索引 — 对话开始时,所有已注册的工具都会被索引到 ToolIndex 中,但不会发送到 LLM
- 初始请求 — 仅将内置的 toolSearchTool 定义发送到 LLM
- 发现调用 — 当 LLM 需要某个功能时,它会使用搜索查询调用 toolSearchTool
- 搜索与扩展 — ToolIndex 找到匹配的工具;它们的定义会被添加到下一个请求中
- 工具调用 — LLM 可以看到 toolSearchTool 和已发现的工具定义,并可以调用实际的工具
- 工具执行 — 已发现的工具被执行,其结果返回给 LLM
- 响应 — LLM 使用工具结果生成最终答案
三种索引策略
| 类型 | 实现类 | 匹配方式 | 适用场景 |
|---|---|---|---|
| 语义 Semantic | VectorToolIndex |
基于嵌入的相似度搜索 | 自然语言描述,需 VectorStore |
| 关键字 Keyword | LuceneToolIndex |
Apache Lucene 精确词项匹配 | 已知工具名称,快速有效 |
| 正则 Regex | RegexToolIndex |
正则表达式匹配 | 工具名称遵循已知命名约定(如 get_*) |
Spring Boot Starter 配置
最简单的方式(包含 Lucene 和自动配置):
1 | <!-- Maven --> |
1 | // Gradle |
或手动使用库:
1 | <dependency> |
配置属性参考
| 属性 | 说明 | 默认值 |
|---|---|---|
spring.ai.chat.client.tool-search-advisor.enabled |
启用 Advisor,为 true 时替换默认 ToolCallingAdvisor |
false |
spring.ai.chat.client.tool-search-advisor.tool-index-type |
索引实现:regex/lucene/vector |
regex |
spring.ai.chat.client.tool-search-advisor.max-results |
每次搜索返回的最大工具引用数 | null(LLM 自行决定) |
spring.ai.chat.client.tool-search-advisor.system-message-suffix |
追加到系统消息的自定义提示后缀 | null(使用内置模板) |
spring.ai.chat.client.tool-search-advisor.reference-tool-name-accumulation |
是否累积所有搜索调用中发现的工具名称 | true |
spring.ai.chat.client.tool-search-advisor.session-id-key-name |
携带会话 ID 的 Advisor 上下文键 | chat_memory_conversation_id |
spring.ai.chat.client.tool-search-advisor.advisor-order |
Advisor 在链中的顺序 | HIGHEST_PRECEDENCE + 300 |
spring.ai.chat.client.tool-search-advisor.eviction.lru-max-sessions |
LRU 驱逐策略保留的最大活跃会话数 | 1000 |
spring.ai.chat.client.tool-search-advisor.eviction.ttl |
空闲会话的生存时间 | null |
spring.ai.chat.client.tool-search-advisor.lucene.min-score-threshold |
Lucene 命中的最低分数阈值 | 0.25 |
一键启用:
1 | spring.ai.chat.client.tool-search-advisor.enabled: true |
ToolSearchToolCallingAdvisor Builder 配置
| 配置项 | 说明 | 默认值 |
|---|---|---|
toolIndex(ToolIndex) |
使用的搜索实现(必填) | — |
maxResults(Integer) |
每次搜索返回的最大工具引用数 | null |
systemMessageSuffix(String) |
追加到系统消息的自定义提示后缀 | 内置模板 |
referenceToolNameAccumulation(boolean) |
是否累积跨搜索调用的工具名称 | true |
sessionIdKeyName(String) |
查找会话 ID 的上下文键 | chat_memory_conversation_id |
evictionStrategy(...) |
会话索引释放策略 | LruEvictionStrategy(1000) |
advisorOrder(int) |
Advisor 在链中的位置 | HIGHEST_PRECEDENCE + 300 |
会话驱逐策略
每次调用按 sessionId 隔离,工具索引在并发对话间相互隔离。
| 策略 | 说明 |
|---|---|
LruEvictionStrategy(maxSessions)(默认) |
超过最大活跃会话数时驱逐最近最少使用的会话 |
NeverEvictStrategy.INSTANCE |
永不自动驱逐,直到显式调用 evictSession() |
AlwaysEvictStrategy.INSTANCE |
每次请求前清除会话索引,强制完全重新索引 |
TtlEvictionStrategy(duration) |
驱逐上次访问时间超过 TTL 的会话 |
CompositeEvictionStrategy(strategies...) |
委托给多个策略,任一策略请求即驱逐 |
驱逐是惰性评估的——不需要后台线程。
适用场景
推荐使用:
- 系统中有 10 个以上工具
- 工具定义消耗超过 10K Token
- 构建连接多个服务器的 MCP 驱动系统
- 大型工具集出现工具选择准确性问题
传统方式可能更好:
- 小型工具库(少于 10 个工具)
- 每个会话中所有工具都被频繁使用
- 工具定义非常紧凑